package org.codefinger.json.parser;

import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

import org.codefinger.json.JSON;
import org.codefinger.json.JSONArray;
import org.codefinger.json.JSONBase;
import org.codefinger.json.JSONObject;
import org.codefinger.json.parser.converter.ConverterFromArray;
import org.codefinger.json.parser.converter.ConverterFromBigDecimal;
import org.codefinger.json.parser.converter.ConverterFromBigInteger;
import org.codefinger.json.parser.converter.ConverterFromBoolean;
import org.codefinger.json.parser.converter.ConverterFromByte;
import org.codefinger.json.parser.converter.ConverterFromChar;
import org.codefinger.json.parser.converter.ConverterFromCollection;
import org.codefinger.json.parser.converter.ConverterFromDate;
import org.codefinger.json.parser.converter.ConverterFromDouble;
import org.codefinger.json.parser.converter.ConverterFromEnum;
import org.codefinger.json.parser.converter.ConverterFromFloat;
import org.codefinger.json.parser.converter.ConverterFromInt;
import org.codefinger.json.parser.converter.ConverterFromJSONBase;
import org.codefinger.json.parser.converter.ConverterFromLong;
import org.codefinger.json.parser.converter.ConverterFromMap;
import org.codefinger.json.parser.converter.ConverterFromPojo;
import org.codefinger.json.parser.converter.ConverterFromShort;
import org.codefinger.json.parser.converter.ConverterFromString;
import org.codefinger.json.parser.deserializer.DeserializerForBigDecimal;
import org.codefinger.json.parser.deserializer.DeserializerForBigInteger;
import org.codefinger.json.parser.deserializer.DeserializerForBoolean;
import org.codefinger.json.parser.deserializer.DeserializerForBooleanArray;
import org.codefinger.json.parser.deserializer.DeserializerForByte;
import org.codefinger.json.parser.deserializer.DeserializerForByteArray;
import org.codefinger.json.parser.deserializer.DeserializerForChar;
import org.codefinger.json.parser.deserializer.DeserializerForCharArray;
import org.codefinger.json.parser.deserializer.DeserializerForCollection;
import org.codefinger.json.parser.deserializer.DeserializerForDate;
import org.codefinger.json.parser.deserializer.DeserializerForDouble;
import org.codefinger.json.parser.deserializer.DeserializerForDoubleArray;
import org.codefinger.json.parser.deserializer.DeserializerForEnum;
import org.codefinger.json.parser.deserializer.DeserializerForFloat;
import org.codefinger.json.parser.deserializer.DeserializerForFloatArray;
import org.codefinger.json.parser.deserializer.DeserializerForInt;
import org.codefinger.json.parser.deserializer.DeserializerForIntArray;
import org.codefinger.json.parser.deserializer.DeserializerForJSON;
import org.codefinger.json.parser.deserializer.DeserializerForJSONBase;
import org.codefinger.json.parser.deserializer.DeserializerForJSONObject;
import org.codefinger.json.parser.deserializer.DeserializerForLong;
import org.codefinger.json.parser.deserializer.DeserializerForLongArray;
import org.codefinger.json.parser.deserializer.DeserializerForMap;
import org.codefinger.json.parser.deserializer.DeserializerForObjArray;
import org.codefinger.json.parser.deserializer.DeserializerForObject;
import org.codefinger.json.parser.deserializer.DeserializerForPojo;
import org.codefinger.json.parser.deserializer.DeserializerForShort;
import org.codefinger.json.parser.deserializer.DeserializerForShortArray;
import org.codefinger.json.parser.deserializer.DeserializerForString;
import org.codefinger.json.util.IdentityCache;
import org.codefinger.json.util.IdentityCache.ValueBuilder;
import org.codefinger.json.util.Lang;
import org.codefinger.json.util.type.Generic;

public class JSONParser {

	private JSONLexer											lexer;

	private static final IdentityCache<Type, JSONDeserializer>	DESERIALIZER_MAP	= new IdentityCache<Type, JSONDeserializer>(new JSONDeserializerBuilder());

	private static final IdentityCache<Type, JSONConverter>		JSONCONVERTER_MAP	= new IdentityCache<Type, JSONConverter>(new JSONConverterBuilder());

	static {
		DESERIALIZER_MAP.put(JSONObject.class, DeserializerForJSONObject.INSTANCE);
		DESERIALIZER_MAP.put(JSONArray.class, DeserializerForJSONObject.INSTANCE);
		DESERIALIZER_MAP.put(JSONBase.class, DeserializerForJSONBase.INSTANCE);
		DESERIALIZER_MAP.put(JSON.class, DeserializerForJSON.INSTANCE);
		DESERIALIZER_MAP.put(Object.class, DeserializerForObject.INSTANCE);

		DESERIALIZER_MAP.put(int[].class, DeserializerForIntArray.INSTANCE);
		DESERIALIZER_MAP.put(float[].class, DeserializerForFloatArray.INSTANCE);
		DESERIALIZER_MAP.put(double[].class, DeserializerForDoubleArray.INSTANCE);
		DESERIALIZER_MAP.put(byte[].class, DeserializerForByteArray.INSTANCE);
		DESERIALIZER_MAP.put(short[].class, DeserializerForShortArray.INSTANCE);
		DESERIALIZER_MAP.put(long[].class, DeserializerForLongArray.INSTANCE);
		DESERIALIZER_MAP.put(boolean[].class, DeserializerForBooleanArray.INSTANCE);
		DESERIALIZER_MAP.put(char[].class, DeserializerForCharArray.INSTANCE);

		DESERIALIZER_MAP.put(int.class, DeserializerForInt.INSTANCE);
		DESERIALIZER_MAP.put(float.class, DeserializerForFloat.INSTANCE);
		DESERIALIZER_MAP.put(double.class, DeserializerForDouble.INSTANCE);
		DESERIALIZER_MAP.put(byte.class, DeserializerForByte.INSTANCE);
		DESERIALIZER_MAP.put(short.class, DeserializerForShort.INSTANCE);
		DESERIALIZER_MAP.put(long.class, DeserializerForLong.INSTANCE);
		DESERIALIZER_MAP.put(boolean.class, DeserializerForBoolean.INSTANCE);
		DESERIALIZER_MAP.put(char.class, DeserializerForChar.INSTANCE);

		DESERIALIZER_MAP.put(Integer.class, DeserializerForInt.INSTANCE);
		DESERIALIZER_MAP.put(Float.class, DeserializerForFloat.INSTANCE);
		DESERIALIZER_MAP.put(Double.class, DeserializerForDouble.INSTANCE);
		DESERIALIZER_MAP.put(Byte.class, DeserializerForByte.INSTANCE);
		DESERIALIZER_MAP.put(Short.class, DeserializerForShort.INSTANCE);
		DESERIALIZER_MAP.put(Long.class, DeserializerForLong.INSTANCE);
		DESERIALIZER_MAP.put(Boolean.class, DeserializerForBoolean.INSTANCE);
		DESERIALIZER_MAP.put(Character.class, DeserializerForChar.INSTANCE);

		DESERIALIZER_MAP.put(BigDecimal.class, DeserializerForBigDecimal.INSTANCE);
		DESERIALIZER_MAP.put(BigInteger.class, DeserializerForBigInteger.INSTANCE);
		DESERIALIZER_MAP.put(Date.class, DeserializerForDate.INSTANCE);
		DESERIALIZER_MAP.put(String.class, DeserializerForString.INSTANCE);

		JSONCONVERTER_MAP.put(int.class, ConverterFromInt.INSTANCE);
		JSONCONVERTER_MAP.put(double.class, ConverterFromDouble.INSTANCE);
		JSONCONVERTER_MAP.put(float.class, ConverterFromFloat.INSTANCE);
		JSONCONVERTER_MAP.put(long.class, ConverterFromLong.INSTANCE);
		JSONCONVERTER_MAP.put(boolean.class, ConverterFromBoolean.INSTANCE);
		JSONCONVERTER_MAP.put(short.class, ConverterFromShort.INSTANCE);
		JSONCONVERTER_MAP.put(byte.class, ConverterFromByte.INSTANCE);
		JSONCONVERTER_MAP.put(char.class, ConverterFromChar.INSTANCE);

		JSONCONVERTER_MAP.put(Integer.class, ConverterFromInt.INSTANCE);
		JSONCONVERTER_MAP.put(Double.class, ConverterFromDouble.INSTANCE);
		JSONCONVERTER_MAP.put(Float.class, ConverterFromFloat.INSTANCE);
		JSONCONVERTER_MAP.put(Long.class, ConverterFromLong.INSTANCE);
		JSONCONVERTER_MAP.put(Boolean.class, ConverterFromBoolean.INSTANCE);
		JSONCONVERTER_MAP.put(Short.class, ConverterFromShort.INSTANCE);
		JSONCONVERTER_MAP.put(Byte.class, ConverterFromByte.INSTANCE);
		JSONCONVERTER_MAP.put(Character.class, ConverterFromChar.INSTANCE);

		JSONCONVERTER_MAP.put(String.class, ConverterFromString.INSTANCE);
		JSONCONVERTER_MAP.put(Date.class, ConverterFromDate.INSTANCE);
		JSONCONVERTER_MAP.put(BigDecimal.class, ConverterFromBigDecimal.INSTANCE);
		JSONCONVERTER_MAP.put(BigInteger.class, ConverterFromBigInteger.INSTANCE);
		JSONCONVERTER_MAP.put(JSONBase.class, ConverterFromJSONBase.INSTANCE);
	}

	public JSONParser(JSONLexer lexer) {
		super();
		this.lexer = lexer;
	}

	@SuppressWarnings("unchecked")
	public <T> T parse(Type type) {
		JSONDeserializer deserializer = getDeserializer(type);
		JSONLexer lexer = this.lexer;
		lexer.nextToken();
		switch (lexer.token()) {
		case STRING:
			return (T) deserializer.stringValue(lexer);
		case INT:
			return (T) deserializer.intValue(lexer.tokenText());
		case FLOAT:
			return (T) deserializer.floatValue(lexer.tokenText());
		case FALSE:
			return (T) deserializer.falseValue();
		case TRUE:
			return (T) deserializer.trueValue();
		case NULL:
		case UNDIFINED:
		case END:
			return null;
		case LEFT_CURLY:
			return (T) parseLeftCurly(deserializer.createObjectDeserializer());
		case LEFT_SQUARE:
			return (T) parseLeftSquare(deserializer.createArrayDeserializer());
		default:
			throw lexer.makeThrow();
		}
	}

	private Object parseLeftCurly(JSONObjectDeserializer objectDeserializer) {
		JSONLexer lexer = this.lexer;
		for (;;) {

			lexer.nextToken();
			switch (lexer.token()) {
			case STRING:
				objectDeserializer.setFieldName(lexer);
				break;
			case FIELD_NAME:
				objectDeserializer.setFieldName(lexer.tokenText());
				break;
			case RIGHT_CURLY:
				return objectDeserializer.getDeserializedValue();
			default:
				throw lexer.makeThrow();
			}

			lexer.nextToken();
			if (lexer.token() == JSONToken.COLON) {

				lexer.nextToken();
				switch (lexer.token()) {
				case STRING:
					objectDeserializer.setStringValue(lexer);
					break;
				case INT:
					objectDeserializer.setIntValue(lexer.tokenText());
					break;
				case FLOAT:
					objectDeserializer.setFloatValue(lexer.tokenText());
					break;
				case FALSE:
					objectDeserializer.setFalseValue();
					break;
				case TRUE:
					objectDeserializer.setTrueValue();
					break;
				case NULL:
				case UNDIFINED:
					break;
				case LEFT_CURLY:
					objectDeserializer.setValue(parseLeftCurly(objectDeserializer.createObjectDeserializer()));
					break;
				case LEFT_SQUARE:
					objectDeserializer.setValue(parseLeftSquare(objectDeserializer.createArrayDeserializer()));
					break;
				default:
					throw lexer.makeThrow();
				}

				lexer.nextToken();
				switch (lexer.token()) {
				case COMMA:
					continue;
				case RIGHT_CURLY:
					return objectDeserializer.getDeserializedValue();
				default:
					throw lexer.makeThrow();
				}

			}

			throw lexer.makeThrow();
		}
	}

	private Object parseLeftSquare(JSONArrayDeserializer arrayDeserializer) {
		JSONLexer lexer = this.lexer;
		for (;;) {

			lexer.nextToken();
			switch (lexer.token()) {
			case STRING:
				arrayDeserializer.addStringValue(lexer);
				break;
			case INT:
				arrayDeserializer.addIntValue(lexer.tokenText());
				break;
			case FLOAT:
				arrayDeserializer.addFloatValue(lexer.tokenText());
				break;
			case FALSE:
				arrayDeserializer.addFalseValue();
				break;
			case TRUE:
				arrayDeserializer.addTrueValue();
				break;
			case NULL:
			case UNDIFINED:
				arrayDeserializer.addNullValue();
				break;
			case LEFT_CURLY:
				arrayDeserializer.addValue(parseLeftCurly(arrayDeserializer.createObjectDeserializer()));
				break;
			case LEFT_SQUARE:
				arrayDeserializer.addValue(parseLeftSquare(arrayDeserializer.createArrayDeserializer()));
				break;
			default:
				throw lexer.makeThrow();
			}

			lexer.nextToken();
			switch (lexer.token()) {
			case COMMA:
				continue;
			case RIGHT_SQUARE:
				return arrayDeserializer.getDeserializedValue();
			default:
				throw lexer.makeThrow();
			}
		}
	}

	public static JSONDeserializer getBasicDeserializer(Type type) {
		if (type == int.class || type == Integer.class) {
			return DeserializerForInt.INSTANCE;
		}
		if (type == String.class) {
			return DeserializerForString.INSTANCE;
		}
		if (type == boolean.class || type == Boolean.class) {
			return DeserializerForBoolean.INSTANCE;
		}
		if (type == long.class || type == Long.class) {
			return DeserializerForLong.INSTANCE;
		}
		if (type == Date.class) {
			return DeserializerForDate.INSTANCE;
		}
		if (type == double.class || type == Double.class) {
			return DeserializerForDouble.INSTANCE;
		}
		if (type == BigDecimal.class) {
			return DeserializerForBigDecimal.INSTANCE;
		}
		if (type == float.class || type == Float.class) {
			return DeserializerForFloat.INSTANCE;
		}
		if (type == BigInteger.class) {
			return DeserializerForBigInteger.INSTANCE;
		}
		if (type == JSONBase.class) {
			return DeserializerForJSONBase.INSTANCE;
		}
		if (type == byte.class || type == Byte.class) {
			return DeserializerForByte.INSTANCE;
		}
		if (type == char.class || type == Character.class) {
			return DeserializerForChar.INSTANCE;
		}
		if (type == short.class || type == Short.class) {
			return DeserializerForShort.INSTANCE;
		}
		if (type instanceof Class) {
			Class<?> clazz = (Class<?>) type;
			if (Enum.class.isAssignableFrom(clazz)) {
				return new DeserializerForEnum(clazz);
			}
		}
		return null;
	}

	public static JSONDeserializer getDeserializer(Type type) {
		return DESERIALIZER_MAP.get(type);
	}

	public static JSONConverter getConverter(Type type) {
		return JSONCONVERTER_MAP.get(type);
	}

	public static Object parseTo(Object value, Type type) {
		if (value == null) {
			return null;
		}
		JSONConverter jsonConverter = getConverter(value.getClass());
		JSONDeserializer deserializer = getDeserializer(type);
		switch (jsonConverter.jsonConverterType()) {
		case Base:
			return jsonConverter.convert(value, deserializer);
		case Object:
			return jsonConverter.convert(value, deserializer.createObjectDeserializer());
		case Array:
			return jsonConverter.convert(value, deserializer.createArrayDeserializer());
		default:
			throw Lang.makeThrow("It is impossible!");
		}
	}

	public static Object parseTo(Object value, JSONDeserializer deserializer) {
		if (value == null) {
			return null;
		}
		JSONConverter jsonConverter = getConverter(value.getClass());
		switch (jsonConverter.jsonConverterType()) {
		case Base:
			return jsonConverter.convert(value, deserializer);
		case Object:
			return jsonConverter.convert(value, deserializer.createObjectDeserializer());
		case Array:
			return jsonConverter.convert(value, deserializer.createArrayDeserializer());
		default:
			throw Lang.makeThrow("It is impossible!");
		}
	}

	public static void setItem(Object value, JSONObjectDeserializer objectDeserializer) {
		if (value == null) {
			return;
		}
		JSONConverter jsonConverter = getConverter(value.getClass());
		switch (jsonConverter.jsonConverterType()) {
		case Base:
			jsonConverter.setItemValue(value, objectDeserializer);
			return;
		case Object:
			objectDeserializer.setValue(//
					jsonConverter.convert(value, objectDeserializer.createObjectDeserializer()));
			return;
		case Array:
			objectDeserializer.setValue(//
					jsonConverter.convert(value, objectDeserializer.createArrayDeserializer()));
			return;
		default:
			throw Lang.makeThrow("It is impossible!");
		}
	}

	public static void addItem(Object value, JSONArrayDeserializer arrayDeserializer) {
		if (value == null) {
			arrayDeserializer.addNullValue();
			return;
		}
		JSONConverter jsonConverter = getConverter(value.getClass());
		switch (jsonConverter.jsonConverterType()) {
		case Base:
			jsonConverter.addItemValue(value, arrayDeserializer);
			return;
		case Object:
			arrayDeserializer.addValue(//
					jsonConverter.convert(value, arrayDeserializer.createObjectDeserializer()));
			return;
		case Array:
			arrayDeserializer.addValue(//
					jsonConverter.convert(value, arrayDeserializer.createArrayDeserializer()));
			return;
		default:
			throw Lang.makeThrow("It is impossible!");
		}
	}

	private static class JSONConverterBuilder implements ValueBuilder<Type, JSONConverter> {

		@Override
		public JSONConverter build(Type type) {
			Generic generic = Generic.getGeneric(type);

			Class<?> clazz = generic.getRealClass();

			if (generic.isArray()) {
				return ConverterFromArray.INSTANCE;
			} else if (clazz.isEnum()) {
				return ConverterFromEnum.INSTANCE;
			} else if (Map.class.isAssignableFrom(clazz)) {
				return ConverterFromMap.INSTANCE;
			} else if (Collection.class.isAssignableFrom(clazz)) {
				return ConverterFromCollection.INSTANCE;
			}

			return new ConverterFromPojo(clazz);
		}

	}

	private static class JSONDeserializerBuilder implements ValueBuilder<Type, JSONDeserializer> {

		@Override
		public JSONDeserializer build(Type type) {
			Generic generic = Generic.getGeneric(type);

			Class<?> clazz = generic.getRealClass();

			if (generic.isArray()) {
				return new DeserializerForObjArray(type);
			} else if (clazz.isEnum()) {
				return new DeserializerForEnum(clazz);
			} else if (Map.class.isAssignableFrom(clazz)) {
				return new DeserializerForMap(type);
			} else if (Collection.class.isAssignableFrom(clazz)) {
				return new DeserializerForCollection(type);
			}

			return new DeserializerForPojo(clazz);
		}

	}

}
